#pragma once
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <vector>
#include <string>
#include <functional>
#include <ctime>
#include "Task.hpp"

const int gdef_process_num = 5; // 创建子进程的个数
using callback_t = std::function<void(int fd)>;

// 描述一个管道
class Channel
{
public:
    Channel() {}
    Channel(int fd, const std::string &name, pid_t id)
        : _wfd(fd), _name(name), _sub_target(id)
    {
    }
    ~Channel() {}

    void Debug()
    {
        printf("channel name: %s, wfd: %d, target id: %d\n", _name.c_str(), _wfd, _sub_target);
    }

    int Fd() { return _wfd; }
    std::string Name() { return _name; }
    pid_t Id() { return _sub_target; }
    void Close() { close(_wfd); }
    void Wait()
    {
        pid_t rid = waitpid(_sub_target, nullptr, 0);
        (void)rid;
    }

private:
    int _wfd;          // 写入文件的文件描述符
    std::string _name; // 管道名
    pid_t _sub_target; // 管道对应的子进程pid
};

// 进程池
class ProcessPool
{
private:
    void CtrlSubProces(int &index)
    {
        // 选择一个信道
        int who = index;
        index++;
        index %= channels.size();

        // 选择一个任务(随机)
        int x = rand() % tasks.size();

        // 任务推送给子进程
        std::cout << "选择信道: " << channels[who].Name() << ", subtarget: " << channels[who].Id() << std::endl;
        write(channels[who].Fd(), &x, sizeof(x));
        sleep(1);
    }

public:
    ProcessPool(int num = gdef_process_num)
        : _num(num)
    {
        srand(time(nullptr) ^ getpid() ^ 0x777);
    }
    ~ProcessPool() {}
    // 初始化进程池
    bool Init_ProcessPool(callback_t cb)
    {
        for (int i = 0; i < _num; ++i) // 这个for循环是由父进程执行
        {
            sleep(1);
            // 创建管道
            int pipefd[2] = {0};
            int n = pipe(pipefd);
            if(n < 0)   return false;

            // 创建子进程
            pid_t id = fork();
            if (id == 0)
            {
                // 子进程还要关闭从父进程继承下来的之前所有子进程的fd
                // 父进程对channels的修改不会影响子进程的channels,谁修改谁就进行写时拷贝
                std::cout << "进程:" << getpid() << ", 关闭了: ";
                for (auto &c : channels)
                {
                    std::cout << c.Fd() << " ";
                    c.Close(); // 把继承下来的wfd关闭就可以
                }
                std::cout << std::endl;

                // 子进程关闭写端
                close(pipefd[1]);

                // 子进程执行
                cb(pipefd[0]);

                exit(0); // 退出
            }

            // 父进程关闭读端
            close(pipefd[0]);
            std::string name = "channel-" + std::to_string(i); // 当前父进程创建的管道名
            channels.emplace_back(pipefd[1], name, id);        // 初始化管道
            // Channel channel(pipefd[1], name, id);
            // channels.push_back(channel);
        }

        return true;
    }

    // 2、唤醒指定一个子进程，控制子进程
    // 2.1、选择一个子进程，本质:选择一个信道
    // 要求：负载均衡,避免个别进程很忙而其他进程很闲
    // 方法：轮询选择

    void PollingCtrlSubProcess()
    {
        int index = 0;
        while (true)
        {
            CtrlSubProces(index);
        }
    }
    void PollingCtrlSubProcess(int n)
    {
        if (n < 0)
            return;
        int index = 0;
        while (n--)
        {
            CtrlSubProces(index);
        }
    }

    void ProcessPoolList()
    {
        std::cout << "进程池wfd list: ";
        for (auto &c : channels)
        {
            std::cout << c.Fd() << " ";
        }
        std::cout << std::endl;
    }

    // 等待子进程退出
    // 管道间交互问题：
    // 子进程会继承父进程的文件描述符表，父进程每次创建子进程和管道时就会往文件描述符中空缺下标内写入新的内容，
    // 所以每次创建的子进程继承到的文件描述符表都是不一样的
    // 因此从第二个子进程开始它的文件描述符表中就会有指向其他子进程所拥有的管道的fd，最后以创建的子进程会包含指向所有创建的管道的fd

    // 解决方法：
    // 1、从最后一子进程开始一一关闭所有子进程
    // 2、先结束所有子进程，在统一回收子进程
    // 3、子进程将每次继承下来的wfd给关闭
    void WaitSubProcess()
    {
        // 1、倒着关闭进程
        //  for(int i = channels.size() - 1; i >= 0; --i)
        //  {
        //      channels[i].Close();
        //      channels[i].Wait();
        //  }

        // 2、
        //  // 1、所有子进程结束
        //  for (auto &c : channels)
        //  {
        //      c.Close(); // 关闭所有写端
        //  }
        //  // 2、回收所有子进程僵尸状态
        //  for (auto &c : channels)
        //  {
        //      c.Wait();
        //      std::cout << "回收子进程：" << c.Id() << std::endl;
        //  }

        // 3、
        for (auto &c : channels)
        {
            c.Close();
            c.Wait();
        }
    }

private:
    std::vector<Channel> channels; // 所有管道
    int _num;                      // 进程池内管道数量
};
